package cz.ctu.guiproject.client.dispatcher;

import static cz.ctu.guiproject.client.resources.constants.Constants.SAX_PREMATURE_END;
import static cz.ctu.guiproject.client.resources.constants.Constants.SERVER_DISCONNECT_RESPONSE;
import static cz.ctu.guiproject.client.resources.constants.Constants.SERVER_FORCE_UI_UPDATE;
import static cz.ctu.guiproject.client.resources.constants.Constants.SERVER_INIT_RESPONSE;
import static cz.ctu.guiproject.client.resources.constants.Constants.SERVER_UI_RESPONSE;
import guiproject.client.view.Event;

import java.io.IOException;
import java.io.StringReader;

import javax.xml.parsers.ParserConfigurationException;
import javax.xml.parsers.SAXParser;
import javax.xml.parsers.SAXParserFactory;

import org.xml.sax.Attributes;
import org.xml.sax.InputSource;
import org.xml.sax.SAXException;
import org.xml.sax.helpers.DefaultHandler;

import android.util.Log;
import cz.ctu.guiproject.client.device.AndroidDevice;
import cz.ctu.guiproject.client.dtos.State;
import cz.ctu.guiproject.client.manager.ConnectionManager;
import cz.ctu.guiproject.client.manager.ConnectionManagerImpl;
import cz.ctu.guiproject.client.manager.JARManager;
import cz.ctu.guiproject.client.manager.JARManagerImpl;
import cz.ctu.guiproject.client.manager.UIManager;
import cz.ctu.guiproject.client.manager.UIManagerImpl;
import cz.ctu.guiproject.client.messaging.InitRequest;
import cz.ctu.guiproject.client.messaging.UIRequest;
import cz.ctu.guiproject.client.networking.ClientNetworkAgent;
import cz.ctu.guiproject.client.networking.ClientNetworkAgentImpl;
import cz.ctu.guiproject.client.networking.ClientNetworkObserver;
import cz.ctu.guiproject.client.resources.constants.Logger;
import cz.ctu.guiproject.client.resources.utils.ConfigProvider;
import cz.ctu.guiproject.client.xml.XmlEncodeFactory;

public final class DispatcherImpl implements ClientNetworkObserver, Dispatcher {

	private static DispatcherImpl instance;
	private ClientNetworkAgent networkAgent;
	private ConnectionManager connectionManager;
	private UIManager uiManager;
	private JARManager jarManager;
	private State connectionState;
	private Object connectionLock;
	private ConfigProvider configProvider;

	private DispatcherImpl() {
	}

	/* (non-Javadoc)
	 * @see cz.ctu.guiproject.client.dispatcher.Dispatcher#setup()
	 */
	@Override
	public void setup() {
		connectionState = State.DISCONNECTED;
		configProvider = ConfigProvider.getInstance();
		final String[] config = configProvider.getConfig();
		networkAgent = new ClientNetworkAgentImpl(config[0], Integer.parseInt(config[1]));
		networkAgent.registerObserver(this);
		connectionManager = new ConnectionManagerImpl(this);
		uiManager = new UIManagerImpl();
		jarManager = JARManagerImpl.getInstance();

		connectionLock = new Object();

		clientMainLoop();
		Log.i(Logger.TAG, "Dispatcher init finished.");
	}

	public static Dispatcher getInstance() {
		if (instance == null) {
			instance = new DispatcherImpl();
		}
		return instance;
	}

	private void pause() {
		synchronized (connectionLock) {
			try {
				connectionLock.wait();
			} catch (InterruptedException e) {
				Log.e(Logger.TAG, "connectionLock released: " + e.getMessage());
			}
		}
	}

	private void resume() {
		synchronized (connectionLock) {
			connectionLock.notify();
		}
	}

	private void clientMainLoop() {
		new Thread(new Runnable() {

			@Override
			public void run() {
				// init connection with server
				final InitRequest initRequest = new InitRequest();
				initRequest.setDeviceName(AndroidDevice.getInstance().getDeviceName());
				initRequest.setInstalledComponents(jarManager.getInstalledComponents());
				final String initRequestXML = XmlEncodeFactory.encodeInitRequest(initRequest);
				send(initRequestXML);
				// set STATE to particular value, force synchronous operation
				connectionState = State.AWAITING_CONNECTION;
				// wait, till the connection has been established
				pause();
				Log.i(Logger.TAG, "Client is now CONNECTED to server.");
				// request UI
				final UIRequest uiRequest = new UIRequest();
				final String uiRequestXML = XmlEncodeFactory.encodeUIRequest(uiRequest);
				send(uiRequestXML);
				connectionState = State.AWAITING_UI;
				pause();
				Log.i(Logger.TAG, "Client is now READY to interact with user.");
			}
		}).start();
	}

	/* (non-Javadoc)
	 * @see cz.ctu.guiproject.client.dispatcher.Dispatcher#send(java.lang.String)
	 */
	@Override
	public void send(String message) {
		networkAgent.send(message);
	}

	@Override
	public void update(Object message) {
		// TODO get rid of [no_of_session]
		String xml = message.toString();
		xml = xml.substring(xml.indexOf(']') + 1);
		// parse XML and get root tag name
		final SAXParserFactory factory = SAXParserFactory.newInstance();
		try {
			final SAXParser saxParser = factory.newSAXParser();
			final DefaultHandler handler = new TagResolver(message.toString());
			saxParser.parse(new InputSource(new StringReader(xml)), handler);
		} catch (ParserConfigurationException e) {
			Log.e(Logger.TAG, e.getMessage());
		} catch (SAXException e) {
			Log.e(Logger.TAG, e.getMessage());
		} catch (IOException e) {
			Log.e(Logger.TAG, e.getMessage());
		}
	}

	private final class TagResolver extends DefaultHandler {

		private final String xml;

		TagResolver(String xml) {
			this.xml = xml;
		}

		@Override
		public void startElement(String uri, String localName, String qName, Attributes attributes) throws SAXException {
			if (qName.equals(SERVER_INIT_RESPONSE)) {
				if (connectionState == State.AWAITING_CONNECTION) {
					if (connectionManager.initClient(xml)) {
						connectionState = State.CONNECTED;
						// in this moment request UI from server
						resume();
					} else {
						connectionState = State.DISCONNECTED;
					}
				}
				throw new SAXException(SAX_PREMATURE_END);
			} else if (qName.equals(SERVER_DISCONNECT_RESPONSE)) {
				connectionManager.disconnectClient(xml);
				throw new SAXException(SAX_PREMATURE_END);
			} else if (qName.equals(SERVER_UI_RESPONSE)) {
				if (uiManager.initUI(xml)) {
					connectionState = State.READY;
					// if everything goes well, client is now ready to interact
					// with user
					resume();
				}
				throw new SAXException(SAX_PREMATURE_END);
			} else if (qName.equals(SERVER_FORCE_UI_UPDATE)) {
				Log.i(Logger.TAG, "pred updatem");
				uiManager.forceUIUpdate(xml);
				Log.i(Logger.TAG, "po updatu");
				throw new SAXException(SAX_PREMATURE_END);
			}
		}
	}

	@Override
	public void update(Event event) {
		Log.i(Logger.TAG, "Event " + event.getEventId() + " occured!");
		final String eventXml = XmlEncodeFactory.encodeEvent(event);
		send(eventXml);
	}

	@Override
	public void update(String viewId, String attrName, String attrValue) {
		final String propagateUIUpdateXml = XmlEncodeFactory.encodePropagateUIUpdate(viewId, attrName, attrValue);
		send(propagateUIUpdateXml);
	}

	/* (non-Javadoc)
	 * @see cz.ctu.guiproject.client.dispatcher.Dispatcher#disconnect()
	 */
	@Override
	public void disconnect() {
		networkAgent.disableReconnect();
		connectionManager.forceDisconnect();
	}
}
